package com.cx.common.rhino;

import com.cx.common.utils.NumberUtil;
import com.cx.common.utils.StringTool;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


public class RhinoHelper {

    private static Logger log = LoggerFactory.getLogger(RhinoHelper.class);

    /**
     * 缓存列表
     */
    private static final LoadingCache<String, Number> FORMULA_EXPRESSION_CACHES =
            CacheBuilder.newBuilder()
                    .maximumSize(10000).expireAfterAccess(60, TimeUnit.MINUTES).build(new
                                                                                              CacheLoader<String, Number>() {
                                                                                                  @Override
                                                                                                  public Number load(String key) throws Exception {
                                                                                                      return null;
                                                                                                  }
                                                                                              });

    private static String[] paramNames = null;

    static {
        int amount = 30;
        paramNames = new String[amount];
        for (int i = 0; i < amount; i++) {
            paramNames[i] = "n" + (i + 1);
        }
    }

    /**
     * 取得参数名
     *
     * @param i 从0开始
     * @return
     */
    public static String getParamName(int i) {
        if (i >= paramNames.length) {
            paramNames = (String[]) ArrayUtils.add(paramNames, "n" + (i + 1));
        }
        return paramNames[i];
    }

    /**
     * 执行公式表达式
     *
     * @param expression 公式表达式
     * @param context    公式执行上下文
     * @return {@link Number} 公式表达式执行结果
     */
    private static Object execute(String expression, Map<String, ?> context) {
        if (!StringUtils.isBlank(expression)) {
            try {
                return Rhino.invoke(expression, context);
            } catch (Exception ex) {
                log.error("公式: [{}], 参数[{}]执行错误 - ", expression, context);
                log.error("", ex);
            }
        }
        return null;
    }

    /**
     * 执行公式表达式
     *
     * @param expression 公式表达式
     * @param context    公式执行上下文
     * @return {@link Number} 公式表达式执行结果
     */
    public static Number invoke(String expression, Map<String, ?> context) {
        Object value = execute(expression, context);
        return value == null ? 0 : (Number) value;
    }

    /**
     * 执行公式表达式
     *
     * @param expression 公式表达式
     * @param ctx        公式执行上下文
     * @param resultType 执行结果类型
     * @return T 公式表达式执行结果
     */
    @SuppressWarnings("unchecked")
    public static <T> T invoke(String expression, Map<String, ?> ctx, Class<T>
            resultType) {
        Object value = execute(expression, ctx);
        if (resultType == Boolean.class || resultType == boolean.class) {
            return (T) (value == null ? false : value);
        } else {
            Number numberValue = value == null ? 0 : (Number) value;
            return NumberUtil.valueOf(resultType, numberValue);
        }
    }

    /**
     * 执行公式表达式. 如果计算结果与需要的返回值类型不一致, 则
     *
     * @param expression 公式表达式
     * @param numbers    公式参数
     * @param resultType 执行结果类型
     * @return T 公式表达式执行结果
     */
    @SuppressWarnings("unchecked")
    public static <T> T invoke(String expression, Class<T> resultType, Number...
            numbers) {
        int len = numbers.length;
        Map<String, Object> ctx = new HashMap<String, Object>(len);
        for (int i = 0; i < len; i++) {
            ctx.put(getParamName(i), numbers[i]);
        }

        Object value = execute(expression, ctx);
        if (resultType == Boolean.class || resultType == boolean.class) {
            return (T) (value == null ? false : value);
        } else {
            Number numberValue = value == null ? 0 : (Number) value;
            return NumberUtil.valueOf(resultType, numberValue);
        }
    }

    /**
     * @param formulaId 公式ID
     * @param params    可变参参数
     * @return {@link Number} 公式计算返回值
     */
    public static Number invoke(String expression, Number... params) {
        if (StringUtils.isBlank(expression)) {
            return 0;
        }

        // 缓存KEY. 并且从缓存中获取
        String paramString = Arrays.toString(params);
        final String CACHE_KEY = toCacheKey(expression, paramString);
        Number result = FORMULA_EXPRESSION_CACHES.getIfPresent(CACHE_KEY);
        if (result != null) {
            return result;
        }

        int len = params.length;
        Map<String, Object> ctx = new HashMap<String, Object>(len);
        for (int i = 0; i < len; i++) {
            ctx.put(getParamName(i), params[i]);
        }

        Object resultValue = RhinoHelper.invoke(expression, ctx);
        if (log.isDebugEnabled()) {
            log.debug("公式表达式: [{}], 参数: [{}], 计算结果: [{}]", new Object[]{expression,
                    paramString, resultValue});
        }

        // 放置到缓存
        result = resultValue == null ? 0 : (Number) resultValue;
        if (resultValue != null) {
            FORMULA_EXPRESSION_CACHES.put(CACHE_KEY, result);
        } else {
            FORMULA_EXPRESSION_CACHES.invalidate(CACHE_KEY);
        }

        return result;
    }

    /**
     * 构建缓存Key
     *
     * @param expression  公式ID
     * @param paramString 公式参数字符串
     * @return {@link String} 返回缓存字符串
     */
    private static String toCacheKey(String expression, String paramString) {
        return StringTool.buildString((expression == null ? StringUtils.EMPTY :
                expression), paramString);
    }

}
